/*
Abstract:
  Program main file

Last changed:
  $Id: main.cpp 87 2009-12-14 22:20:23Z vitamin.caig $

Author:
  (C) Vitamin/CAIG/2001

  This file is the part of Textator project
*/

#include <error.h>
#include <symbol_check.h>

#include "io/logging.h"
#include "io/streams.h"
#include "io/dataprovider.h"
#include "parse/parser.h"
#include "packing/packer.h"
#include "analyze/analyzer.h"
#include "output/outputter.h"
#include "synthesize/synthesizer.h"

#include <cassert>
#include <numeric>
#include <fstream>
#include <iostream>
#include <algorithm>

#include <text.h>
#include <cmdline.h>

#define FILE_TAG 041AA091

namespace
{
  //analyze assumes
  const std::size_t MIN_REF_SIZE = 1;
  const std::size_t MAX_REF_SIZE = 3;

  const String::value_type EMPTY[] = {'\0'};

  const std::size_t TAB_DEFAULT = 8;
  const std::size_t WIDTH_DEFAULT = 80;
  const std::size_t MINSIZE_DEFAULT = 5;
  const std::size_t MAXSIZE_DEFAULT = 64;
  const std::size_t MINREFS_DEFAULT = 4;

  template<class V>
  inline void Convert(const String& str, V& val)
  {
    std::basic_istringstream<String::value_type> stream(str);
    if (!(stream >> val))
    {
      throw Error(ERROR_DETAIL, 1,
        StringFormatter::Process(TEXT_ERROR_INVALID_VALUE, str));
    }
  }

  std::size_t SumInputSize(std::size_t inVal, const StringMap::value_type& str)
  {
    return IsSpecialToken(str.first) ? inVal : inVal + str.second.size();
  }

  class Setup
  {
  private:
    Setup(const Setup&);
    Setup& operator = (const Setup&);
  public:
    typedef std::vector<String> Args;
    Setup()
      : DoProcess(false), DoTokenize(false), DoPack(false), DoStatistic(false),
      TokenizeMethod(BY_WORDS), OutMode(TEXT)
    {
      //init defaults

      //input

      //tokenize
      MinTokenSize = MINSIZE_DEFAULT;
      MaxTokenSize = MAXSIZE_DEFAULT;
      MinTokenRefs = MINREFS_DEFAULT;

      //output
      AsmParams.DefB = DEFB_DEFAULT;
      AsmParams.Equ = EQU_DEFAULT;
      AsmParams.Inline = false;
      AsmParams.Macro = MACRO_DEFAULT;
      AsmParams.EndMacro = ENDMACRO_DEFAULT;
      AsmParams.Tab = TAB_DEFAULT;
      AsmParams.Width = WIDTH_DEFAULT;
      CPPParams.SymbolType = SYMBOLTYPE_DEFAULT;
      CPPParams.MemoryType = MEMTYPE_DEFAULT;
      CPPParams.Inline = true;
      CPPParams.Width = WIDTH_DEFAULT;
      CPPParams.Tab = TAB_DEFAULT;
    }
    
    bool ParseCmdline(const Args& args, const String& version)
    {
      if (1 == args.size())
      {
        Log::Message(TEXT_HELP, args[0], Log::DEFAULT);
        Log::Message(TEXT_HELP_COMMON_PARAMS, Log::DEFAULT);
        Log::Message(TEXT_HELP_ANALYZE_PARAMS, MINSIZE_DEFAULT, MINREFS_DEFAULT, MAXSIZE_DEFAULT, Log::DEFAULT);
        Log::Message(TEXT_HELP_EXPORT_PARAMS, TAB_DEFAULT, WIDTH_DEFAULT, Log::DEFAULT);
	return false;
      }
      Args::const_iterator par = args.begin(), lim = args.end();
      ++par;
      while (par != lim)
      {
        const String& key(*par);
	
	if (key == VERSION_KEY)
	{
	  Log::Message(TEXT_VERSION, version, Log::DEFAULT);
	  return false;
	}
	
        ++par;
        const bool hasParam(lim != par);
        const String& val(hasParam ? *par : EMPTY);
        if (key == OPTIMIZE_KEY)
        {
          DoProcess = DoTokenize = DoPack = false;
        }
        else if (key == PROCESS_KEY)
        {
          DoProcess = true;
          DoTokenize = false;
        }
        else if (key == TOKENIZE_KEY)
        {
          DoProcess = DoTokenize = true;
          DoPack = false;
        }
        else if (key == PACKING_KEY)
        {
          DoProcess = true;
          DoTokenize = false;
          DoPack = true;
          PackMethod = val;
          ++par;
        }
        else if (key == STATISTIC_KEY)
        {
          DoStatistic = true;
        }
        else if (key == ASMMODE_KEY)
        {
          OutMode = ASSEMBLER;
        }
        else if (key == TXTMODE_KEY)
        {
          OutMode = TEXT;
        }
        else if (key == CPPMODE_KEY)
        {
          OutMode = CPP;
        }
        else if (key == VERBOSE_KEY)
        {
          Log::SetLogLevel(Log::VERBOSE);
        }
        else if (key == DEBUG_KEY)
        {
          Log::SetLogLevel(Log::DEBUG);
        }
        else if (key == OUTFILE_KEY)
        {
          OutFileName = val;
          ++par;
        }
        else if (key == KEYS_KEY)
        {
          SplitString(val, std::not1(std::ptr_fun(Symbol::IsKey)), ParseParams.Keys);
          ++par;
        }
        else if (key == WIDTH_KEY)
        {
          unsigned wid;
          Convert(val, wid);
          AsmParams.Width = CPPParams.Width = wid;
          ++par;
        }
        else if (key == TAB_KEY)
        {
          unsigned tab;
          Convert(val, tab);
          AsmParams.Tab = CPPParams.Tab = tab;
          ++par;
        }
        else if (key == DEFB_KEY)
        {
          AsmParams.DefB = val;
          ++par;
        }
        else if (key == EQU_KEY)
        {
          AsmParams.Equ = val;
          ++par;
        }
        else if (key == INLINE_KEY)
        {
          AsmParams.Inline = CPPParams.Inline = true;
        }
        else if (key == NOINLINE_KEY)
        {
          AsmParams.Inline = CPPParams.Inline = false;
        }
        else if (key == MACRO_KEY)
        {
          AsmParams.Macro = val;
          ++par;
        }
        else if (key == ENDMACRO_KEY)
        {
          AsmParams.EndMacro = val;
          ++par;
        }
        else if (key == WORDS_KEY)
        {
          TokenizeMethod = BY_WORDS;
        }
        else if (key == FRAGMENTS_KEY)
        {
          TokenizeMethod = BY_FRAGMENTS;
        }
        else if (key == MINSIZE_KEY)
        {
          Convert(val, MinTokenSize);
          ++par;
        }
        else if (key == MAXSIZE_KEY)
        {
          Convert(val, MaxTokenSize);
          ++par;
        }
        else if (key == MINREFS_KEY)
        {
          Convert(val, MinTokenRefs);
          ++par;
        }
        else if (key == SYMBOLTYPE_KEY)
        {
          CPPParams.SymbolType = val;
          ++par;
        }
        else if (key == MEMTYPE_KEY)
        {
          CPPParams.MemoryType = val;
          ++par;
        }
        else
        {
          if (!val.empty())
          {
            throw Error(ERROR_DETAIL, 1,
              StringFormatter::Process(TEXT_ERROR_INVALID_PARAMETER, key));
          }
          InFileName = key;
        }
      }
      if (InFileName.empty())
      {
        throw Error(ERROR_DETAIL, 1, TEXT_ERROR_NO_SOURCE_FILENAME);
      }
      return true;
    }

    void Parse(EntriesMap& tokenized)
    {
      if (ASSEMBLER == OutMode)
      {
        ParseParams.Keys.push_back(ASSEMBLER_AUTO_KEY);
      }
      else if (CPP == OutMode)
      {
        ParseParams.Keys.push_back(CPP_AUTO_KEY);
      }
      Parser::Parse(*IO::SourceDataProvider::Create(InFileName), ParseParams, tokenized);
    }

    void Process(const EntriesMap& src, StringMap& dst)
    {
      if (DoProcess)
      {
        Synthesizer::Process(src, dst);
      }
    }

    void Tokenize(const StringMap& src, EntriesMap& dst)
    {
      if (!DoTokenize)
      {
        return;
      }
      //check input parameters
      for (std::size_t tkSize = MIN_REF_SIZE; tkSize <= MAX_REF_SIZE; ++tkSize)
      {
        if (MinTokenSize * MinTokenRefs < MinTokenSize + 1 + MinTokenRefs * tkSize)
        {
          Log::Message(TEXT_WARNING_TOKENIZE_PARAMETERS, tkSize);
        }
      }
      StringMap optimized;
      Analyzer::Statistic::Aliasing alstat;
      Analyzer::ByStrings(src, dst, optimized, alstat);
      Analyzer::Statistic::Tokenizing tkstat;
      if (BY_WORDS == TokenizeMethod)
      {
        Analyzer::ByWordsParameters params;
        params.MinWordSize = MinTokenSize;
        params.MinWordsReferences = MinTokenRefs;
        Analyzer::ByWords(optimized, params, dst, tkstat);
      }
      else if (BY_FRAGMENTS == TokenizeMethod)
      {
        Analyzer::ByFragmentsParameters params;
        params.MinFragmentSize = MinTokenSize;
        params.MaxFragmentSize = MaxTokenSize;
        params.MinFragmentReferences = MinTokenRefs;
        Analyzer::ByFragments(optimized, params, dst, tkstat);
      }
      else
      {
        assert(!"Invalid tokenize method");
      }

      if (DoStatistic)
      {
        const std::size_t srcStrings(src.size());
        const std::size_t srcSymbols(std::accumulate(src.begin(), src.end(), 0, SumInputSize));
        Log::Message(TEXT_INPUT_DATA_STATISTIC, srcStrings, srcSymbols, Log::DEFAULT);
        Log::Message(TEXT_ALIASING_STATISTIC, alstat.AliasesCount, alstat.AliasedSymbols, Log::DEFAULT);
        Log::Message(TEXT_TOKENIZING_STATISTIC,
          tkstat.TokensCount, tkstat.TokensSymbols, tkstat.TokensReferences, tkstat.TokenizedSymbols, tkstat.MaxTokenSize, Log::DEFAULT);

        //ratio statistic for assembler output without inlining
        if (ASSEMBLER == OutMode && !AsmParams.Inline)
        {
          const std::size_t inBytes(srcSymbols + srcStrings);//symbols + zeroes
          const std::size_t outBytesBase(srcSymbols
            - alstat.AliasedSymbols              //saved on aliases
            + (srcStrings - alstat.AliasesCount) //zeroes for remain strings
            - tkstat.TokenizedSymbols            //saved on tokenizing
            + tkstat.TokensSymbols               //data for tokens
            + tkstat.TokensCount                 //zeroes for tokens
            );
          Log::Message(TEXT_COMPRESSION_STATISTIC, inBytes, Log::DEFAULT);
          for (std::size_t refSize = 1; refSize <= 3; ++refSize)
          {
            const std::size_t outBytes(outBytesBase + refSize * tkstat.TokensReferences);
            Log::Message(TEXT_COMPRESSION_STATISTIC_ITEM,
              outBytes, 100 * outBytes / inBytes, refSize, Log::DEFAULT);
          }
        }
      }
    }

    void Pack(const StringMap& src, EntriesMap& pck)
    {
      if (!DoPack)
      {
        return;
      }

      StringMap optimized;
      Analyzer::Statistic::Aliasing alstat;
      Analyzer::ByStrings(src, pck, optimized, alstat);
      Packer::Statistic::Packing stat;
      if (PackMethod == DIZPACK_KEY)
      {
        Packer::DizPack(optimized, pck, stat);
      }
      else
      {
        throw Error(ERROR_DETAIL, 1,
          StringFormatter::Process(TEXT_ERROR_INVALID_PARAMETER, PackMethod));
      }
      if (DoStatistic)
      {
        const std::size_t srcStrings(src.size());
        const std::size_t srcSymbols(std::accumulate(src.begin(), src.end(), 0, SumInputSize));
        const std::size_t inBytes = srcStrings + srcSymbols;//strings and zeroes
        Log::Message(TEXT_INPUT_DATA_STATISTIC, srcStrings, srcSymbols, Log::DEFAULT);
        Log::Message(TEXT_ALIASING_STATISTIC, alstat.AliasesCount, alstat.AliasedSymbols, Log::DEFAULT);
        Log::Message("Packed data: $1 bytes\nSupplementary data: $2 bytes\nTotal: $3 bytes (ratio $4%)\n",
          stat.ResultSize, stat.SupplementSize, stat.ResultSize + stat.SupplementSize,
          100 * (stat.ResultSize + stat.SupplementSize) / inBytes, Log::DEFAULT);
      }
    }

    void Output(const StringMap& str, const EntriesMap& tok)
    {
      assert(ASSEMBLER == OutMode || TEXT == OutMode || CPP == OutMode ||
        !"Invalid output mode specified");
      Output::Outputter::Ptr out;
      //auto_ptr workarounds for VS2003
      if (ASSEMBLER == OutMode)
      {
        out.reset(Output::CreateAssemblerOutputter(AsmParams).release());
      }
      else if (CPP == OutMode)
      {
        out.reset(Output::CreateCPPOutputter(CPPParams).release());
      }
      else
      {
        out.reset(Output::CreateTextOutputter().release());
      }

      IO::TargetStream::Ptr stream(IO::TargetStream::Create(OutFileName));
      if (DoTokenize || DoPack || !DoProcess)//tokenized
      {
        out->Process(tok, *stream);
      }
      else
      {
        out->Process(str, *stream);
      }
    }

  private:
    bool DoProcess;
    bool DoTokenize;
    bool DoPack;
    String PackMethod;
    bool DoStatistic;
    //input
    String InFileName;
    Parser::Parameters ParseParams;
    //tokenize
    enum
    {
      BY_WORDS,
      BY_FRAGMENTS
    } TokenizeMethod;
    std::size_t MinTokenSize;
    std::size_t MaxTokenSize;
    std::size_t MinTokenRefs;
    //output
    Output::AssemblerParameters AsmParams;
    Output::CPPParameters CPPParams;
    String OutFileName;
    enum
    {
      TEXT,
      ASSEMBLER,
      CPP
    } OutMode;
  };
}

//streams
#ifdef UNICODE
InStream& StdIn(std::wcin);
OutStream& StdOut(std::wcout);
OutStream& StdErr(std::wcerr);
#else
InStream& StdIn(std::cin);
OutStream& StdOut(std::cout);
OutStream& StdErr(std::cerr);
#endif

#ifndef TEXTATOR_VERSION
#define TEXTATOR_VERSION develop
#endif

#define STR(a) #a

#define VERSION_STRING(a) STR(a)

int main(int argc, char* argv[])
{
  try
  {
    Setup::Args args(argc);
    std::transform(argv, argv + argc, args.begin(), FromStdString);
    Setup setup;
    if (setup.ParseCmdline(args, FromStdString(VERSION_STRING(TEXTATOR_VERSION))))
    {
      EntriesMap tokenized;
      StringMap instantiated;
      setup.Parse(tokenized);
      setup.Process(tokenized, instantiated);
      setup.Tokenize(instantiated, tokenized);
      setup.Pack(instantiated, tokenized);
      setup.Output(instantiated, tokenized);
    }
    return 0;
  }
  catch (const Error& e)
  {
    Log::ErrorMessage(e);
    return static_cast<int>(e.Code);
  }
}
